using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Web.Caching;
using Rogan.Utility.Common;

namespace Rogan.Data
{
	public class EntityQueryable<T> : IEntityQueryable<T>, IEntityQueryable where T : Entity
	{
		protected DbTransaction trans;
		public DbProvider DbProvider
		{
			get
			{
				return database.DbProvider;
			}
		}
		internal Database database
		{
			get;
			private set;
		}
		public EntityQueryExpression Expression
		{
			get;
			set;
		}
		internal Entity Table
		{
			get;
			set;
		}
		private bool enabledCache
		{
			get
			{
				return DbProvider.CacheConfig.Enable && DbProvider.EntitiesCache.ContainsKey(EntityHelper.GetName<T>());
			}
		}
		private CacheInfo cacheInfo
		{
			get
			{
				if (enabledCache)
				{
					return null;
				}
				return DbProvider.EntitiesCache[EntityHelper.GetName<T>()];
			}
		}
		public EntityQueryable(Database db)
		{
			database = db;
			Expression = new EntityQueryExpression(DataHelper.Create<T>());
		}
		public EntityQueryable(Database db, DbTransaction trans) : this(db)
		{
			this.trans = trans;
		}
		public EntityQueryable<T> Select(params Field[] fields)
		{
			Expression.AddSelect(fields);
			return this;
		}
		public EntityQueryable<T> Where(WhereExpression expression)
		{
			Expression.AddWhere(expression);
			return this;
		}
		public EntityQueryable<T> OrderBy(params OrderByExpression[] exps)
		{
			Expression.AddOrderBy(exps);
			return this;
		}
		public EntityQueryable<T> GroupBy(params Field[] exps)
		{
			Expression.AddGroupBy(exps);
			return this;
		}
		public EntityQueryable<T> Having(WhereExpression exp)
		{
			Expression.AddGroupByHaving(exp);
			return this;
		}
		public EntityQueryable<T> Distinct()
		{
			Expression.IsDistinct = new bool?(true);
			return this;
		}
		public EntityQueryable<T> LeftJoin<TJOIN>(WhereExpression expression) where TJOIN : Entity
		{
			return Join<TJOIN>(expression, JoinType.LeftJoin);
		}
		public EntityQueryable<T> RightJoin<TJOIN>(WhereExpression expression) where TJOIN : Entity
		{
			return Join<TJOIN>(expression, JoinType.RightJoin);
		}
		public EntityQueryable<T> InnerJoin<TJOIN>(WhereExpression expression) where TJOIN : Entity
		{
			return Join<TJOIN>(expression, JoinType.InnerJoin);
		}
		public EntityQueryable<T> CrossJoin<TJOIN>(WhereExpression expression) where TJOIN : Entity
		{
			return Join<TJOIN>(expression, JoinType.CrossJoin);
		}
		public EntityQueryable<T> Join<TJOIN>(WhereExpression expression) where TJOIN : Entity
		{
			return Join<TJOIN>(expression, JoinType.InnerJoin);
		}
		public EntityQueryable<T> Join<TJOIN>(WhereExpression expression, JoinType joinType) where TJOIN : Entity
		{
			TJOIN tJOIN = DataHelper.Create<TJOIN>();
			JoinExpression joinExp = new JoinExpression(tJOIN, expression, joinType);
			Expression.AddJoin(tJOIN, joinExp);
			return this;
		}
		public EntityQueryable<T> Skip(int skip)
		{
			Expression.StartIndex = new int?(skip);
			return this;
		}
		public EntityQueryable<T> Top(int top)
		{
			if (!Expression.StartIndex.HasValue)
			{
				Expression.StartIndex = new int?(0);
			}
			Expression.EndIndex = Expression.StartIndex + top;
			return this;
		}
		public EntityQueryable<T> Page(int pageSize, int pageNumber)
		{
			Expression.StartIndex = new int?((pageNumber - 1) * pageSize);
			Expression.EndIndex = new int?(pageNumber * pageSize - 1);
			return this;
		}
		protected IDataReader ToDataReader(DbCommand cmd)
		{
			if (trans == null)
			{
				return database.ExecuteReader(cmd);
			}
			return database.ExecuteReader(cmd, trans);
		}
		protected object ExecuteScalar(DbCommand cmd)
		{
			if (trans == null)
			{
				return database.ExecuteScalar(cmd);
			}
			return database.ExecuteScalar(cmd, trans);
		}
		protected DataSet ToDataSet(DbCommand cmd)
		{
			if (trans == null)
			{
				return database.ExecuteDataSet(cmd);
			}
			return database.ExecuteDataSet(cmd, trans);
		}
		private void AddCache(string key, object obj)
		{
			if (!enabledCache)
			{
				return;
			}
			CacheInfo cacheInfo = this.cacheInfo;
			if (cacheInfo.TimeOut.HasValue)
			{
				CacheHelper.Insert(key, obj, cacheInfo.TimeOut.Value);
				return;
			}
            if (!string.IsNullOrEmpty(cacheInfo.FilePath))
			{
				CacheHelper.Insert(key, obj, new CacheDependency(cacheInfo.FilePath));
			}
		}
		private object getCache(string key)
		{
			if (!enabledCache)
			{
				return null;
			}
			return CacheHelper.Get(key);
		}
		private void removeCache(string key)
		{
			if (!enabledCache)
			{
				return;
			}
			CacheHelper.Remove(key);
		}
		private void SetOneRecord()
		{
			if (Expression.IsSkip)
			{
				Expression.EndIndex = new int?(Expression.StartIndex.Value + 1);
				return;
			}
			Expression.EndIndex = new int?(1);
		}
		private DbCommand CreateCurrentCommand()
		{
			ExpressionTranslater expressionTranslater = new ExpressionTranslater(DbProvider, Expression);
			expressionTranslater.Translate();
			return DbProvider.CreateCommand(expressionTranslater.CommandText, expressionTranslater.Params.ToArray());
		}
		private string GetCacheKey(string type, string commandText)
		{
			return DbProvider.ConnectionStringsName + ("_" + type + "_") + commandText;
		}
		public int Count()
		{
			SetOneRecord();
			Expression.Select.Fields = new List<Field>
			{
				new Field("count(*)").As("cnt")
			};
			DbCommand dbCommand = CreateCurrentCommand();
			string cacheKey = GetCacheKey("Count", dbCommand.CommandText);
			if (enabledCache)
			{
				object cache = getCache(cacheKey);
				if (cache != null)
				{
					return (int)cache;
				}
			}
			int num = (int)ExecuteScalar(dbCommand);
			if (enabledCache)
			{
				AddCache(cacheKey, num);
			}
			return num;
		}
		public bool Any()
		{
			return Count() > 0;
		}
		public bool Exist()
		{
			return Count() > 0;
		}
		public object ToScalar()
		{
			SetOneRecord();
			DbCommand dbCommand = CreateCurrentCommand();
			string cacheKey = GetCacheKey("ToScalar", dbCommand.CommandText);
			if (enabledCache)
			{
				object cache = getCache(cacheKey);
				if (cache != null)
				{
					return cache;
				}
			}
			object obj = ExecuteScalar(dbCommand);
			if (enabledCache)
			{
				AddCache(cacheKey, obj);
			}
			return obj;
		}
		public TObject ToScalar<TObject>()
		{
			return DataHelper.ConvertValue<TObject>(ToScalar());
		}
		public T ToFirst()
		{
			SetOneRecord();
			DbCommand dbCommand = CreateCurrentCommand();
			string cacheKey = GetCacheKey("ToFirst", dbCommand.CommandText);
			object cache = getCache(cacheKey);
			if (cache != null)
			{
				return cache as T;
			}
			T t = EntityHelper.CreateTable<T>();
			using (IDataReader dataReader = ToDataReader(dbCommand))
			{
				if (dataReader.Read())
				{
					List<Field> selectFields = Expression.SelectFields;
					foreach (Field field in selectFields)
					{
						if (!field.PropertyName.Contains("*") && (string.IsNullOrEmpty(field.AliasName) || !(field.AliasName != field.PropertyName)))
						{
							if (t.GetFields().Any((Field c) => c.PropertyName == field.PropertyName))
							{
								t.SetValue(field.PropertyName, dataReader[field.PropertyName]);
							}
						}
					}
				}
			}
			AddCache(cacheKey, t);
			return t;
		}
		public List<T> ToList()
		{
			DbCommand dbCommand = CreateCurrentCommand();
			string cacheKey = GetCacheKey("ToList", dbCommand.CommandText);
			object cache = getCache(cacheKey);
			if (cache != null)
			{
				return cache as List<T>;
			}
			List<T> list = new List<T>();
			List<Field> selectFields = Expression.SelectFields;
			using (IDataReader dataReader = ToDataReader(dbCommand))
			{
				while (dataReader.Read())
				{
					T item = EntityHelper.CreateTable<T>();
					foreach (Field field in selectFields)
					{
						if (!field.PropertyName.Contains("*") && (string.IsNullOrEmpty(field.AliasName) || !(field.AliasName != field.PropertyName)))
						{
							if (item.GetFields().Any((Field c) => c.PropertyName == field.PropertyName) && !Convert.IsDBNull(dataReader[field.PropertyName]))
							{
								item.SetValue(field.PropertyName, dataReader[field.PropertyName]);
							}
						}
					}
					list.Add(item);
				}
			}
			AddCache(cacheKey, list);
			return list;
		}
		public DataSet ToDataSet()
		{
			DbCommand dbCommand = CreateCurrentCommand();
			string cacheKey = GetCacheKey("ToDataSet", dbCommand.CommandText);
			object cache = getCache(cacheKey);
			if (cache != null)
			{
				return cache as DataSet;
			}
			DataSet dataSet = ToDataSet(dbCommand);
			AddCache(cacheKey, dataSet);
			return dataSet;
		}
		public DataTable ToDataTable()
		{
			DbCommand cmd = CreateCurrentCommand();
			DataSet dataSet = ToDataSet(cmd);
			if (dataSet == null || dataSet.Tables.Count == 0)
			{
				return null;
			}
			return dataSet.Tables[0];
		}
	}
}
